/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.liquidByte.trafficMustang.server.dao;


import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.criteria.*;
import org.liquidByte.trafficMustang.server.dataModel.*;
import org.liquidByte.trafficMustang.server.dataModel.enums.DeslocamentoSituacao;
import org.liquidByte.trafficMustang.server.repository.DeslocamentoRepository;

/**
 *
 * @author Alexandre
 */
@Stateless
public class DeslocamentoDAO extends PersistableDAO<Deslocamento> implements DeslocamentoRepository {

    public DeslocamentoDAO() {
        super(Deslocamento.class);
    }

    public List<Deslocamento> get(Motorista motorista, DeslocamentoSituacao... situacao) {
        EntityManager em = this.getEntityManager();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        
        CriteriaQuery<Deslocamento> q = cb.createQuery(Deslocamento.class);
        Root<Deslocamento> root = q.from(Deslocamento.class);
        q.select(root).where(cb.equal(root.get("motrId"), motorista),
                             root.get("deslcSitId").in((Object[])situacao));
        
        return em.createQuery(q).getResultList();
    }

    public List<Deslocamento> getProximos(Localizacao atualAprox, double limDistAtual, Localizacao destinoAprox, double limDistDestino, Date limiteRastreamento) {
        EntityManager em = this.getEntityManager();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        
        // Query sob os deslocamentos
        CriteriaQuery<Deslocamento> q = cb.createQuery(Deslocamento.class);
        Root<Deslocamento> r = q.from(Deslocamento.class);

        // Filtro de um rastreamento dentro do limite e seguindo a aproximação
        // do limDistAtual para a posição atualAprox, com base nisso, traz
        // os motorista que satisfazem essas condições
        Subquery<Integer> qMot = q.subquery(Integer.class);
        Root<Motorista> rMot = qMot.from(Motorista.class);
        Join<Usuario, Rastreamento> jUsrRastr = rMot.join(Motorista_.usuario)
                                                    .join(Usuario_.rastreamentoList);
        qMot.select(rMot.get(Motorista_.usrId))
            .distinct(true)
            .where(cb.greaterThanOrEqualTo(jUsrRastr.get(Rastreamento_.rastreamentoPK)
                                                    .get(RastreamentoPK_.rastrDt), limiteRastreamento),
                   cb.lessThanOrEqualTo(this.geoDistance(cb,
                                                         jUsrRastr.get(Rastreamento_.localizacao).get(Localizacao_.latitude),
                                                         jUsrRastr.get(Rastreamento_.localizacao).get(Localizacao_.longitude),
                                                         cb.literal(atualAprox.getLatitude()),
                                                         cb.literal(atualAprox.getLongitude())),
                                        new BigDecimal(limDistAtual)));
        
        
        // Busca os deslocamentos em transito que possuem motorista no filtro 
        // especificado acima e que tem destino do deslocamento semelhante a 
        // localização passada pelo destinoAprox seguindo a distancia máxima limDistDestino
        // e que não se encontram lotados
        q.select(r)
         .where(cb.in(r.get(Deslocamento_.motrId).get(Motorista_.usrId)).value(qMot),
                cb.equal(r.get(Deslocamento_.deslcSitId), DeslocamentoSituacao.EmTransito),
                cb.lessThanOrEqualTo(this.geoDistance(cb,
                                                      r.get(Deslocamento_.localizacao).get(Localizacao_.latitude),
                                                      r.get(Deslocamento_.localizacao).get(Localizacao_.longitude),
                                                      cb.literal(destinoAprox.getLatitude()),
                                                      cb.literal(destinoAprox.getLongitude())),
                                     new BigDecimal(limDistDestino)),
                cb.lessThan(r.get(Deslocamento_.deslcLotc), r.get(Deslocamento_.veicId).get(Veiculo_.veicLotc)));

        return em.createQuery(q).getResultList();
    }
    
    private Expression<BigDecimal> geoDistance(CriteriaBuilder cb, Expression<?> srcLat, Expression<?> srcLong,
                                                                   Expression<?> dstLat, Expression<?> dstLong) {
        final BigDecimal EARTH_RAY = new BigDecimal(6371.005076123);

        Expression<BigDecimal> srcLatRad = this.radians(cb, srcLat);
        Expression<BigDecimal> srcLongRad = this.radians(cb, srcLong);
        Expression<BigDecimal> dstLatRad = this.radians(cb, dstLat);
        Expression<BigDecimal> dstLongRad = this.radians(cb, dstLong);
        
        Expression<BigDecimal> cosArc = cb.sum(cb.prod(cb.prod(this.cos(cb, srcLatRad),
                                                               this.cos(cb, dstLatRad)),
                                                               this.cos(cb, cb.diff(srcLongRad, dstLongRad))),
                                               cb.prod(this.sin(cb, srcLatRad),
                                                       this.sin(cb, dstLatRad)));
        
        return cb.prod(EARTH_RAY, this.acos(cb, cosArc));
    }
    private Expression<BigDecimal> radians(CriteriaBuilder cb, Expression<?> value) {
        return cb.function("radians", BigDecimal.class, value);
    }
    private Expression<BigDecimal> sin(CriteriaBuilder cb, Expression<?> value) {
        return cb.function("sin", BigDecimal.class, value);
    }
    private Expression<BigDecimal> cos(CriteriaBuilder cb, Expression<?> value) {
        return cb.function("cos", BigDecimal.class, value);
    }
    private Expression<BigDecimal> acos(CriteriaBuilder cb, Expression<?> value) {
        return cb.function("acos", BigDecimal.class, value);
    }
}
